using System;
using System.Collections.Generic;
using System.Text;

using Toenda.Foundation.Utility;

namespace Toenda.Foundation.Types {
	/// <summary>
	/// Specifies the version of the ISBN code.
	/// </summary>
	public enum ISBNVersion {
		/// <summary>
		/// The ISBN code with 10 characters.
		/// </summary>
		ISBN10 = 0,
		/// <summary>
		/// The newer ISBN code with 13 characters.
		/// </summary>
		ISBN13 = 1
	}

	/// <summary>
	/// Struct ISBN
	/// </summary>
	public struct ISBN {
		private ISBNVersion _version;
		private string _raw_number;

		private string _prefix;
		private string _country_code;
		private string _publisher_number;
		private string _number;
		private string _check_number;

		private int _publisher_number_length;

		// ---------------------------------------------------
		// CONSTRUCTORS
		// ---------------------------------------------------

		/// <summary>
		/// Default Ctor
		/// </summary>
		/// <param name="number"></param>
		public ISBN(string number) {
			this._raw_number = "";

			this._version = ISBNVersion.ISBN13;

			this._prefix = "";
			this._country_code = "";
			this._publisher_number = "";
			this._number = "";
			this._check_number = "";

			this._publisher_number_length = -1;

			this._Parse(number);
		}

		/// <summary>
		/// Default Ctor
		/// </summary>
		/// <param name="number"></param>
		/// <param name="publisherNumberLength">The length of the number for the publisher</param>
		public ISBN(string number, int publisherNumberLength) {
			this._raw_number = "";

			this._version = ISBNVersion.ISBN13;

			this._prefix = "";
			this._country_code = "";
			this._publisher_number = "";
			this._number = "";
			this._check_number = "";

			this._publisher_number_length = publisherNumberLength;

			this._Parse(number);
		}

		/// <summary>
		/// Specific Ctor
		/// </summary>
		/// <param name="isbn"></param>
		public ISBN(ISBN isbn) {
			this._raw_number = isbn._raw_number;
			this._version = isbn._version;

			this._prefix = isbn._prefix;
			this._country_code = isbn._country_code;
			this._publisher_number = isbn._publisher_number;
			this._number = isbn._number;
			this._check_number = isbn._check_number;

			this._publisher_number_length = isbn._publisher_number_length;
		}

		// ---------------------------------------------------
		// INTERFACE IMPLEMENTATIONS
		// ---------------------------------------------------

		// ---------------------------------------------------
		// PROPERTIES
		// ---------------------------------------------------

		/// <summary>
		/// Get or set a raw ISBN number
		/// </summary>
		public string RawNumber {
			get { return this._raw_number; }
			set { this._raw_number = value; }
		}

		/// <summary>
		/// Get or set the version of this ISBN number
		/// </summary>
		public ISBNVersion Version {
			get { return this._version; }
			set { this._version = value; }
		}

		/// <summary>
		/// Get or set the prefix
		/// </summary>
		public string Prefix {
			get { return this._prefix; }
			set { this._prefix = value; }
		}

		/// <summary>
		/// Get or set the country code
		/// </summary>
		public string CountryCode {
			get { return this._country_code; }
			set { this._country_code = value; }
		}

		/// <summary>
		/// Get or set the publisher number
		/// </summary>
		public string PublisherNumber {
			get { return this._publisher_number; }
			set { this._publisher_number = value; }
		}

		/// <summary>
		/// Get or set a correct ISBN number
		/// </summary>
		public string Number {
			get { return this._number; }
			set { this._number = value; }
		}

		/// <summary>
		/// Get or set the check number
		/// </summary>
		public string CheckNumber {
			get { return this._check_number; }
			set { this._check_number = value; }
		}

		/// <summary>
		/// Get a value that indicates if this number is a correct number
		/// </summary>
		public bool IsValid {
			get {
				if(this._version == ISBNVersion.ISBN10) {
					if(this._country_code != null
					&& this._country_code.Trim() != ""
					&& this._number != null
					&& this._number.Trim() != "") {
					//&& this._check_number == this._GetCheckNumber()) {
						return true;
					}
					else {
						return false;
					}
				}
				else {
					if(this._prefix != null
					&& this._prefix.Trim() != ""
					&& (this._prefix.Trim() == "978" || this._prefix.Trim() == "979")
					&& this._country_code != null
					&& this._country_code.Trim() != ""
					&& this._number != null
					&& this._number.Trim() != "") {
					//&& this._check_number == this._GetCheckNumber()) {
						return true;
					}
					else {
						return false;
					}
				}
			}
		}

		/// <summary>
		/// Get a value that indicates if this number is a correct number with the correct check code.
		/// </summary>
		public bool IsValidNumber {
			get {
				if(this._check_number != this._GetCheckNumber()) {
					return false;
				}
				else {
					return true;
				}
			}
		}

		/// <summary>
		/// Get a correct ISBN number from the value
		/// </summary>
		public string Value {
			get {
				StringBuilder str = new StringBuilder();

				switch(this._version) {
					case ISBNVersion.ISBN10:
						//
						break;

					case ISBNVersion.ISBN13:
						if(this._prefix != null
						&& this._prefix.Trim() != "") {
							str.Append(this._prefix);
							str.Append("-");
						}
						break;
				}

				if(this._country_code != null
				&& this._country_code.Trim() != "") {
					str.Append(this._country_code);
					str.Append("-");
				}

				if(this._publisher_number != null
				&& this._publisher_number.Trim() != "") {
					str.Append(this._publisher_number);
					str.Append("-");
				}

				if(this._number != null
				&& this._number.Trim() != "") {
					str.Append(this._number);
					str.Append("-");
				}

				if(this._check_number != null
				&& this._check_number.Trim() != "") {
					if(this.IsValidNumber) {
						str.Append(this._check_number);
					}
					else {
						this.CorrectCheckNumber();
						str.Append(this._check_number);
					}
				}

				return str.ToString();
			}
		}

		/// <summary>
		/// Get a correct ISBN 10 number from the value
		/// </summary>
		public string ISBN10 {
			get {
				StringBuilder str = new StringBuilder();

				if(this._country_code != null
				&& this._country_code.Trim() != "") {
					str.Append(this._country_code);
					str.Append("-");
				}

				if(this._publisher_number != null
				&& this._publisher_number.Trim() != "") {
					str.Append(this._publisher_number);
					str.Append("-");
				}

				if(this._number != null
				&& this._number.Trim() != "") {
					str.Append(this._number);
					str.Append("-");
				}

				if(this._check_number != null
				&& this._check_number.Trim() != "") {
					if(this.IsValidNumber) {
						str.Append(this._check_number);
					}
					else {
						this.CorrectCheckNumber();
						str.Append(this._check_number);
					}
				}

				return str.ToString();
			}
		}

		/// <summary>
		/// Get a correct ISBN 13 number from the value
		/// </summary>
		public string ISBN13 {
			get {
				StringBuilder str = new StringBuilder();

				switch(this._version) {
					case ISBNVersion.ISBN10:
						if(this._prefix != null
						&& this._prefix.Trim() != "") {
							//str.Append(this._prefix);
							// currently only 978
							str.Append("978-");
						}
						break;

					case ISBNVersion.ISBN13:
						if(this._prefix != null
						&& this._prefix.Trim() != "") {
							str.Append(this._prefix);
							str.Append("-");
						}
						break;
				}

				if(this._country_code != null
				&& this._country_code.Trim() != "") {
					str.Append(this._country_code);
					str.Append("-");
				}

				if(this._publisher_number != null
				&& this._publisher_number.Trim() != "") {
					str.Append(this._publisher_number);
					str.Append("-");
				}

				if(this._number != null
				&& this._number.Trim() != "") {
					str.Append(this._number);
					str.Append("-");
				}

				if(this._check_number != null
				&& this._check_number.Trim() != "") {
					if(this.IsValidNumber) {
						str.Append(this._check_number);
					}
					else {
						this.CorrectCheckNumber();
						str.Append(this._check_number);
					}
				}

				return str.ToString();
			}
		}

		/// <summary>
		/// Get a correct EAN number from the value
		/// </summary>
		public string EAN {
			get {
				StringBuilder str = new StringBuilder();

				if(this._prefix != null
				&& this._prefix.Trim() != "") {
					str.Append(this._prefix);
				}

				if(this._country_code != null
				&& this._country_code.Trim() != "") {
					str.Append(this._country_code);
				}

				if(this._publisher_number != null
				&& this._publisher_number.Trim() != "") {
					str.Append(this._publisher_number);
				}

				if(this._number != null
				&& this._number.Trim() != "") {
					str.Append(this._number);
				}

				if(this._check_number != null
				&& this._check_number.Trim() != "") {
					if(this.IsValidNumber) {
						str.Append(this._check_number);
					}
					else {
						this.CorrectCheckNumber();
						str.Append(this._check_number);
					}
				}

				return str.ToString();
			}
		}

		// ---------------------------------------------------
		// EVENTS
		// ---------------------------------------------------

		// ---------------------------------------------------
		// PRIVATE MEMBERS
		// ---------------------------------------------------

		/// <summary>
		/// Parse a number
		/// </summary>
		/// <param name="rawnumber">The rawnumber.</param>
		private void _Parse(string rawnumber) {
			this._raw_number = rawnumber;
			int check = 0;
			int index = 0;
			int len = 0;
			int cut = 0;
			string[] array;
			bool parse = false;

			string tmp = this._ConvertToNumberString(rawnumber);
			len = tmp.Length;

			if(rawnumber.Contains("-")) {
				array = rawnumber.Split(new char[] { '-' });
				
				if(array.GetUpperBound(0) == 3) {
					if(len > 10) {
						this._version = ISBNVersion.ISBN13;
						this._prefix = array[0];
						this._country_code = array[1];
						this._publisher_number = array[2];
						this._number = array[3];
						this._check_number = this._GetCheckNumber();
					}
					else {
						this._version = ISBNVersion.ISBN10;
						this._prefix = "";
						this._country_code = array[0];
						this._publisher_number = array[1];
						this._number = array[2];
						this._check_number = array[3];

						//this.CorrectCheckNumber();
					}
				}
				else if(array.GetUpperBound(0) == 4) {
					this._version = ISBNVersion.ISBN13;
					this._prefix = array[0];
					this._country_code = array[1];
					this._publisher_number = array[2];
					this._number = array[3];
					this._check_number = array[4];

					//this.CorrectCheckNumber();
				}
				else {
					rawnumber = this._ConvertToNumberString(rawnumber);
					this._raw_number = rawnumber;
					parse = true;
				}
			}
			else {
				parse = true;
			}

			if(parse) {
				// prefix
				// version
				switch(len) {
					case 9:
					case 10:
						index = 0;
						this._prefix = "";
						this._version = ISBNVersion.ISBN10;
						break;

					case 12:
					case 13:
						index = 3;
						this._prefix = tmp.Substring(0, 3);
						this._version = ISBNVersion.ISBN13;
						break;
				}

				// check number
				this._check_number = this._GetCheckNumber();

				// country code
				// publisher number
				// number
				this._publisher_number = "";

				check = rawnumber.Substring(index, 1).ToInt32();

				if(len == 10 || len == 13) {
					cut = 1;
				}
				else {
					cut = 0;
				}

				if(check < 8) {
					this._country_code = rawnumber.Substring(index, 1);
					this._number = rawnumber.Substring(
						index + 1, len - ( index + 1 + cut )
					);
				}
				else if(check == 8) {
					this._country_code = rawnumber.Substring(index, 2);
					this._number = rawnumber.Substring(
						index + 2, len - ( index + 2 + cut )
					);
				}
				else if(check == 9) {
					// currently only three chars
					this._country_code = rawnumber.Substring(index, 3);
					this._number = rawnumber.Substring(
						index + 3, len - ( index + 3 + cut )
					);
				}

				// publisher number
				if(this._publisher_number_length > 0) {
					this._publisher_number = this._number.Substring(
						0, 
						this._publisher_number_length
					);
					this._number = this._number.Substring(
						this._publisher_number_length
					);
				}
			}
		}

		/// <summary>
		/// Convert a isbn number string (optional with "-") to a number string
		/// </summary>
		/// <param name="isbn">The isbn.</param>
		/// <returns></returns>
		private string _ConvertToNumberString(string isbn) {
			return isbn.Replace("-", "");
		}

		/// <summary>
		/// Get the ISBN check number
		/// </summary>
		/// <returns></returns>
		private string _GetCheckNumber() {
			string no = this._ConvertToNumberString(this._raw_number);

			switch(no.Length) {
				case 9:
				case 10:
					no = no.Substring(0, 9);
					break;

				case 12:
				case 13:
					no = no.Substring(0, 12);
					break;
			}

			int check = 0;
			int i = 0;
			int len = no.Length;
			int clear = 0;
			string value = "";

			switch(this._version) {
				case ISBNVersion.ISBN10:
					i = 0;

					while(i < len) {
						clear = no[i].ToString().ToInt32();
						check += (clear * ( i + 1 ));

						i++;
					}

					check = check % 11;

					if(check == 10) {
						value = "X";
					}
					else if(check == 11) {
						value = "0";
					}
					else {
						value = check.ToString();
					}
					break;

				case ISBNVersion.ISBN13:
					i = 1;

					foreach(char chr in no.ToCharArray()) {
						clear = chr.ToString().ToInt32();

						if(Validator.IsOddNumbered(i)) {
							check += clear;
						}
						else {
							check += (clear * 3);
						}

						i++;
					}

					check = (10 - (check % 10)) % 10;

					if(check == 10) {
						value = "0";
					}
					else {
						value = check.ToString();
					}
					break;
			}

			return value;
		}

		/// <summary>
		/// Get the complete ISBN number
		/// </summary>
		/// <returns></returns>
		private string _GetCompleteNumber() {
			return this._number
				+ _check_number;
		}

		// ---------------------------------------------------
		// PROTECTED MEMBERS
		// ---------------------------------------------------

		// ---------------------------------------------------
		// PUBLIC MEMBERS
		// ---------------------------------------------------

		/// <summary>
		/// Returns the ISBN as string
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> containing a fully qualified type name.
		/// </returns>
		public override string ToString() {
			switch(this._version) {
				case ISBNVersion.ISBN10:
					return this.ISBN10;

				case ISBNVersion.ISBN13:
					return this.ISBN13;

				default:
					return "";
			}
		}

		/// <summary>
		/// Parse a string into a ISBN
		/// </summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public ISBN Parse(string value) {
			return new ISBN(value);
		}

		/// <summary>
		/// If the check number is not valid, correct it
		/// </summary>
		public void CorrectCheckNumber() {
			if(this._check_number != this._GetCheckNumber()) {
				this._check_number = this._GetCheckNumber();
			}
		}
	}
}
